package com.example.mybatis.executor.impl;

import com.example.mybatis.annotation.Param;
import com.example.mybatis.config.Config;
import com.example.mybatis.constant.MapperSqlType;
import com.example.mybatis.exception.MybatisException;
import com.example.mybatis.executor.Executor;
import com.example.mybatis.handler.param.ParameterHandler;
import com.example.mybatis.handler.result.ResultHandler;
import com.example.mybatis.mapper.MapperMethod;
import com.example.mybatis.mapper.MapperSqlTemplate;
import com.example.mybatis.mapper.component.MapperForeachProperty;
import com.example.mybatis.support.replace.ISqlReplace;
import com.example.mybatis.support.replace.SqlReplaceResult;
import com.example.mybatis.support.replace.impl.ForeachSqlReplace;
import com.example.mybatis.support.replace.impl.IfSqlReplace;
import com.example.mybatis.support.replace.impl.PlaceholderSqlReplace;
import com.example.mybatis.support.replace.impl.SqlReplaceChains;
import com.github.houbb.heaven.support.pipeline.Pipeline;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.util.ArrayUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 这里可以设置为是否使用插件模式。
 * <p>
 * query 查询
 * create 插入
 * edit 更新
 * remove 删除
 *
 * @author binbin.hou
 * @since 0.0.1
 */
public class SimpleExecutor implements Executor {
    private static final Log LOG = LogFactory.getLog(SimpleExecutor.class);

    /**
     * 查询信息
     *
     * @param args   参数
     * @param <T>    泛型
     * @return 结果
     * @since 0.0.1
     */
    @Override
    public <T> T query(Config conifg, MapperMethod mapperMethod, Object[] args) {

        //基本信息
        final Map<String, Object> paramMap = buildParamNameValue(mapperMethod, args);
        //进行参数的替换
        SqlReplaceResult replaceResult = deReplace(mapperMethod, paramMap);
        List<String> paMames = replaceResult.psNames();
        String sql = buildPsSql(replaceResult.dynamicSqlItems());
        //后续可以考虑加入一个拦截器，是否需要
        LOG.info("[Executor Query] [SQL={}] [ARGS={}]", sql, Arrays.toString(args));
        try (Connection connection = conifg.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            //处理参数
            ParameterHandler parameterHandler = new ParameterHandler(preparedStatement, conifg);
            //对args进行一次加工，只保留顺序一致，必须的信息
            parameterHandler.setParams(paMames, paramMap);
            //执行方法
            preparedStatement.execute();
            //处理结果
            ResultSet resultSet = preparedStatement.getResultSet();
            ResultHandler resultHandler = new ResultHandler(conifg, mapperMethod);
            Object result = resultHandler.buildResult(resultSet);
            return (T) result;
        } catch (SQLException ex) {
            throw new MybatisException(ex);
        }
        //return null;
    }

    private String buildPsSql(List<MapperSqlTemplate> dynamicSqlItems) {
        StringBuilder sqlBuffer = new StringBuilder();
        for (MapperSqlTemplate mapperSqlTemplate : dynamicSqlItems) {
            if (mapperSqlTemplate.isReadyForSql()) {
                String sqlTrim = mapperSqlTemplate.getSql().trim();
                sqlBuffer.append(sqlTrim).append(StringUtil.BLANK);
            }
        }
        return sqlBuffer.toString().trim();
    }
    /**
     * 执行替换
     *
     * TODO: 动态替换会导致不同的条件值不对的问题。
     * 避免对原来的 mapper 产生影响。
     *
     * 1. 这里还存在一个问题，如果 test 中包含 include 依赖的话，那么顺序应该再次调整下。
     *
     * include 这个可以先替换掉。
     *
     * test
     * placeholder
     *
     * @param mapperMethod 方法信息
     * @param paramMap 参数信息
     * @return 替换结果
     * @since 0.0.16
     */
    private SqlReplaceResult deReplace(MapperMethod mapperMethod, Map<String, Object> paramMap) {

        List<MapperSqlTemplate> dynamicSqlItems = deepCopySqlItems(mapperMethod);

        SqlReplaceResult sqlReplaceResult = SqlReplaceResult.newInstance().mapperMethod(mapperMethod).paramMap(paramMap).dynamicSqlItems(dynamicSqlItems);

        ISqlReplace replace = new SqlReplaceChains() {

            @Override
            protected void init(Pipeline<ISqlReplace> pipeline) {
                //1. 首先处理 if 判断
                pipeline.addLast(new IfSqlReplace());
                //2. 处理 foreach
                pipeline.addLast(new ForeachSqlReplace());
                //3. 然后处理 占位符
                pipeline.addLast(new PlaceholderSqlReplace());
            }
        };
        return replace.replace(sqlReplaceResult);
    }
    /**
     * 深度拷贝
     *
     * @param mapperMethod 方法
     * @return 结果
     * @since 0.0.16
     */
    private List<MapperSqlTemplate> deepCopySqlItems(MapperMethod mapperMethod) {
        List<MapperSqlTemplate> originalSqlItems = mapperMethod.getSqlItemList();
        List<MapperSqlTemplate> dynamicSqlItems = new ArrayList<>(originalSqlItems.size());
        for (MapperSqlTemplate old : originalSqlItems) {
            MapperSqlTemplate dynamic = new MapperSqlTemplate();
            dynamic.setType(old.getType());
            dynamic.setSql(old.getSql());
            dynamic.setReadyForSql(old.getReadyForSql());
            dynamic.setTestCondition(old.getTestCondition());
            dynamic.setRefId(old.getRefId());

            //property
            if (MapperSqlType.FOREACH.equals(old.getType())) {
                MapperForeachProperty oldP = old.getForeachProperty();
                MapperForeachProperty property = MapperForeachProperty.newInstance().separator(oldP.separator()).collection(oldP.collection())
                        .open(oldP.open()).close(oldP.close()).item(oldP.item()).index(oldP.index());
                dynamic.setForeachProperty(property);
            }
            dynamicSqlItems.add(dynamic);
        }
        return dynamicSqlItems;
    }

    /**
     * 构建参数与名称的映射关系
     *
     * @param mapperMethod 映射名称
     * @param args         参数关系
     * @return 结果
     * @since 0.0.13
     */
    private Map<String, Object> buildParamNameValue(MapperMethod mapperMethod, Object[] args) {
        Map<String, Object> paramMap = new HashMap<>();
        if (ArrayUtil.isEmpty(args)) {
            return paramMap;
        }
        //获取参数信息
        Method method = mapperMethod.getMethod();
        Annotation[][] annotations = method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            String paramName = "arg" + i;
            String annotationValue = getParamValue(annotations[i]);
            if (StringUtil.isNotEmpty(annotationValue)) {
                paramName = annotationValue;
            }
            paramMap.put(paramName, args[i]);
        }
        return paramMap;
    }

    /**
     * 获取参数对应的值
     *
     * @param annotations 注解列表
     * @return 参数名称
     * @since 0.0.13
     */
    private String getParamValue(Annotation[] annotations) {
        if (ArrayUtil.isEmpty(annotations)) {
            return StringUtil.EMPTY;
        }
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(Param.class)) {
                Param param = (Param) annotation;
                return param.value();
            }
        }
        return StringUtil.EMPTY;
    }

    @Override
    public int update(Config config, MapperMethod method, Object[] args) {
       //基本信息
        final Map<String,Object>   paramMap = buildParamNameValue(method,args);
        //进行参数的替换
        SqlReplaceResult sqlReplaceResult = deReplace(method,paramMap);
        //2.1
        List<String>   paNames =     sqlReplaceResult.psNames();
        //2.2sql
         String sql =        buildPsSql(sqlReplaceResult.dynamicSqlItems());
        LOG.info("[Executor update] [SQL={}] [ARGS={}]", sql, Arrays.toString(args));
        try (Connection connection = config.getConnection();  PreparedStatement preparedStatement=  connection.prepareStatement(sql)){
        //处理参数
            ParameterHandler parameterHandler = new ParameterHandler(preparedStatement,config);
          //进行一次加工，只保留和顺序一致的，必须的信息
            parameterHandler.setParams(paNames,paramMap);
      return  preparedStatement.executeUpdate();
        }catch (SQLException ex){
         throw  new MybatisException(ex);
        }
    }
}
